// SPDX-License-Identifier: GPL-2.0
/*
 * OHCI HCD (Host Controller Driver) for USB.
 *
 * (C) Copyright 2010 Hisilicon
 *
 * This file is licenced under the GPL.
 */
#ifdef CONFIG_HIUSB_OHCI
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/platform_device.h>
#include <linux/signal.h>
#include <linux/dma-mapping.h>

#include "hiusb_ont.h"

extern void hiusb_start_hcd(void);
extern void hiusb_stop_hcd(void);
extern int ohci_resume(struct usb_hcd *hcd, bool hibernated);

struct resource g_ohci_res[] = {
	[0] = {
		.start = CONFIG_HIUSB_OHCI_IOBASE,
		.end   = CONFIG_HIUSB_OHCI_IOBASE + CONFIG_HIUSB_OHCI_IOSIZE - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = CONFIG_HIUSB_OHCI_IRQNUM,
		.end   = CONFIG_HIUSB_OHCI_IRQNUM,
		.flags = IORESOURCE_IRQ,
	},
};

u64 g_usb_dmamask_ohci = DMA_BIT_MASK(32);
struct platform_device g_ohci_platdev = {
	.name = "hiusb-ohci",
	.id   = 0,
	.dev  = {
		.platform_data     = 0,
		.dma_mask          = &g_usb_dmamask_ohci,
		.coherent_dma_mask = DMA_BIT_MASK(32),
		.release           = 0,
	},
	.num_resources = ARRAY_SIZE(g_ohci_res),
	.resource      = g_ohci_res,
};

int hiusb_ohci_init(void)
{
	int ret = 0;

	ret = ehci_and_ohci_chip_id_check();
	if (ret != 0)
		return -ENODEV;

	ret = platform_device_register(&g_ohci_platdev);
	if (ret != 0) {
		pr_err("%s->%d, platform_device_register fail.\n", __func__, __LINE__);
		return -ENODEV;
	}

	return 0;
}

int hiusb_driver_register(struct platform_driver *driver)
{
	int ret;

	ret = hiusb_ohci_init();
	if (ret != 0)
		return -ENODEV;

	ret = platform_driver_register(driver);
	if (ret != 0)
		return -ENODEV;

	return 0;
}

void hiusb_driver_unregister(struct platform_driver *driver)
{
	platform_device_unregister(&g_ohci_platdev);

	platform_driver_unregister(driver);
}

static int hiusb_ohci_start(struct usb_hcd *hcd)
{
	struct ohci_hcd *ohci = hcd_to_ohci(hcd);
	int ret = 0;

	ret = ohci_init(ohci);
	if (ret != 0)
		return ret;

	ret = ohci_run(ohci);
	if (ret != 0) {
		ohci_stop(hcd);
		return ret;
	}

	return 0;
}

static const struct hc_driver g_ohci_hc_driver = {
	.description      = hcd_name,
	.product_desc     = "hiusb-ohci",
	.hcd_priv_size =    sizeof(struct ohci_hcd),

	/* generic hardware linkage
	 */
	.irq   = ohci_irq,
	.flags = HCD_MEMORY | HCD_DMA | HCD_USB11,

	/*
	 * basic lifecycle operations
	 */
	.start    = hiusb_ohci_start,
	.stop     = ohci_stop,
	.shutdown = ohci_shutdown,

	/*
	 * managing i/o requests and associated device resources
	 */
	.urb_enqueue      = ohci_urb_enqueue,
	.urb_dequeue      = ohci_urb_dequeue,
	.endpoint_disable = ohci_endpoint_disable,

	/*
	 * scheduling support
	 */
	.get_frame_number = ohci_get_frame,

	/*
	 * root hub support
	 */
	.hub_status_data = ohci_hub_status_data,
	.hub_control     = ohci_hub_control,
#ifdef CONFIG_PM
	.bus_suspend = ohci_bus_suspend,
	.bus_resume  = ohci_bus_resume,
#endif
	.start_port_reset = ohci_start_port_reset,
};

static int hiusb_ohci_hcd_drv_probe(struct platform_device *pdev)
{
	int ret;
	struct usb_hcd *hcd;

	ret = hiusb_ehci_ohci_chip_id_check(g_ohci_res, NULL, NULL, USB_TYPE_ONT_OHCI);
	if (ret != 0)
		return ret;

	if (usb_disabled())
		return -ENODEV;

	if (pdev->resource[1].flags != IORESOURCE_IRQ) {
		pr_debug("resource[1] is not IORESOURCE_IRQ\n");
		return -ENOMEM;
	}

	hcd = usb_create_hcd(&g_ohci_hc_driver, &pdev->dev, "hiusb-ohci");
	if (!hcd)
		return -ENOMEM;

	hcd->rsrc_start = pdev->resource[0].start;
	hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1;
	if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
		pr_debug("request_mem_region failed\n");
		return -EBUSY;
	}

	hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
	if (!hcd->regs) {
		pr_debug("ioremap failed\n");
		release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
		usb_put_hcd(hcd);
		return -ENOMEM;
	}

	hiusb_start_hcd();
	ohci_hcd_init(hcd_to_ohci(hcd));

	ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_SHARED);
	if (ret != 0) {
		hiusb_stop_hcd();
		iounmap(hcd->regs);
		release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
		usb_put_hcd(hcd);
		return ret;
	}

	platform_set_drvdata(pdev, hcd);
	return 0;
}

static int hiusb_ohci_hcd_drv_remove(struct platform_device *pdev)
{
	struct usb_hcd *hcd = platform_get_drvdata(pdev);

	usb_remove_hcd(hcd);
	hiusb_stop_hcd();
	iounmap(hcd->regs);
	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
	usb_put_hcd(hcd);
	platform_set_drvdata(pdev, NULL);

	return 0;
}

#ifdef CONFIG_PM
static int hiusb_ohci_hcd_drv_suspend(struct device *dev)
{
	struct usb_hcd *hcd = dev_get_drvdata(dev);
	struct ohci_hcd *ohci = hcd_to_ohci(hcd);
	unsigned long flags;
	int rc = 0;

	/* Root hub was already suspended. Disable irq emission and
	 * mark HW unaccessible, bail out if RH has been resumed. Use
	 * the spinlock to properly synchronize with possible pending
	 * RH suspend or resume activity.
	 *
	 * This is still racy as hcd->state is manipulated outside of
	 * any locks =P But that will be a different fix.
	 */
	spin_lock_irqsave(&ohci->lock, flags);
	if (hcd->state != HC_STATE_SUSPENDED) {
		rc = -EINVAL;
		goto bail;
	}

	ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
	(void)ohci_readl(ohci, &ohci->regs->intrdisable);

	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
	hiusb_stop_hcd();
bail:
	spin_unlock_irqrestore(&ohci->lock, flags);

	return rc;
}

static int hiusb_ohci_hcd_drv_resume(struct device *dev)
{
	struct usb_hcd *hcd = dev_get_drvdata(dev);

	hiusb_start_hcd();

	// ohci_finish_controller_resume(hcd);
	ohci_resume(hcd, false);

	return 0;
}

static const struct dev_pm_ops hiusb_ohci_pmops = {
	.suspend    = hiusb_ohci_hcd_drv_suspend,
	.resume     = hiusb_ohci_hcd_drv_resume,
};

#define HIUSB_OHCI_PMOPS (&hiusb_ohci_pmops)

#else
#define HIUSB_OHCI_PMOPS NULL
#endif

static struct platform_driver g_ohci_driver = {
	.probe       = hiusb_ohci_hcd_drv_probe,
	.remove      = hiusb_ohci_hcd_drv_remove,
	.shutdown    = usb_hcd_platform_shutdown,
	.driver      = {
		.name    = "hiusb-ohci",
		.owner   = THIS_MODULE,
		.pm      = HIUSB_OHCI_PMOPS,
	},
};

MODULE_ALIAS("platform:hiusb-ohci");

#endif /* CONFIG_HIUSB_OHCI */
